﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using INFDTM01.Models;

namespace INFDTM01.Services
{
    public class UserRecommendationService
    {
        /*
         * Alle "item ids" en "ratings" worden eerst ongesorteerd opgeslagen en vervolgens beide gesorteerd.
         * Dit is handig omdat ze beide met dezelfde index worden opgeslagen.
         * 
         */
        public Dictionary<int, UserPreference> CreateStandardUsers()
        {
            Dictionary<int, UserPreference> userPreferences = new Dictionary<int, UserPreference>();

            //UserPreference userPreference1 = new UserPreference();
            //UserPreference userPreference2 = new UserPreference();
            //UserPreference userPreference3 = new UserPreference();

            //userPreference1.UserId = 1;
            //userPreference1.setItemIdRating(1, 5.0f);
            //userPreference1.setItemIdRating(2, 3.0f);
            //userPreference1.setItemIdRating(3, 2.0f);

            //userPreference2.UserId = 2;
            //userPreference2.setItemIdRating(1, 3.0f);
            //userPreference2.setItemIdRating(2, 4.0f);

            //userPreference3.UserId = 3;
            //userPreference3.setItemIdRating(2, 2.0f);
            //userPreference3.setItemIdRating(3, 5.0f);

            UserPreference userPreference1 = new UserPreference();
            UserPreference userPreference2 = new UserPreference();
            UserPreference userPreference3 = new UserPreference();
            UserPreference userPreference4 = new UserPreference();
            UserPreference userPreference5 = new UserPreference();
            UserPreference userPreference6 = new UserPreference();
            UserPreference userPreference7 = new UserPreference();

            userPreference1.UserId = 1;
            userPreference1.setItemIdRating(101, 2.5f);
            userPreference1.setItemIdRating(102, 3.5f);
            userPreference1.setItemIdRating(103, 3.0f);
            userPreference1.setItemIdRating(104, 3.5f);
            userPreference1.setItemIdRating(105, 2.5f);
            userPreference1.setItemIdRating(106, 3.0f);

            userPreference2.UserId = 2;
            userPreference2.setItemIdRating(101, 3.0f);
            userPreference2.setItemIdRating(102, 3.5f);
            userPreference2.setItemIdRating(103, 1.5f);
            userPreference2.setItemIdRating(104, 5.0f);
            userPreference2.setItemIdRating(105, 3.5f);
            userPreference2.setItemIdRating(106, 3.0f);

            userPreference3.UserId = 3;
            userPreference3.setItemIdRating(101, 2.5f);
            userPreference3.setItemIdRating(102, 3.0f);
            userPreference3.setItemIdRating(104, 3.5f);
            userPreference3.setItemIdRating(106, 4.0f);

            userPreference4.UserId = 4;
            userPreference4.setItemIdRating(102, 3.5f);
            userPreference4.setItemIdRating(103, 3.0f);
            userPreference4.setItemIdRating(104, 4.0f);
            userPreference4.setItemIdRating(105, 2.5f);
            userPreference4.setItemIdRating(106, 4.5f);

            userPreference5.UserId = 5;
            userPreference5.setItemIdRating(101, 3.0f);
            userPreference5.setItemIdRating(102, 4.0f);
            userPreference5.setItemIdRating(103, 2.0f);
            userPreference5.setItemIdRating(104, 3.0f);
            userPreference5.setItemIdRating(105, 2.0f);
            userPreference5.setItemIdRating(106, 3.0f);

            userPreference6.UserId = 6;
            userPreference6.setItemIdRating(101, 3.0f);
            userPreference6.setItemIdRating(102, 4.0f);
            userPreference6.setItemIdRating(104, 5.0f);
            userPreference6.setItemIdRating(105, 3.5f);
            userPreference6.setItemIdRating(106, 3.0f);

            userPreference7.UserId = 7;
            userPreference7.setItemIdRating(102, 4.5f);
            userPreference7.setItemIdRating(104, 4.0f);
            userPreference7.setItemIdRating(105, 1.0f);

            // Dictionary key hetzelfde als de "user id" uit de UserPreference
            userPreferences.Add(1, userPreference1);
            userPreferences.Add(2, userPreference2);
            userPreferences.Add(3, userPreference3);
            userPreferences.Add(4, userPreference4);
            userPreferences.Add(5, userPreference5);
            userPreferences.Add(6, userPreference6);
            userPreferences.Add(7, userPreference7);

            return userPreferences;
        }

        /*
         * Params: 
         *          int userId
         *          Dictionary<int, UserPreference> userPreferences
         * 
         * Returns:
         *          Dictionary<int, double> userSimilarities
         * 
         */
        public Dictionary<int, double> calculateAllSimilarities(int userId, Dictionary<int, UserPreference> userPreferences)
        {
            UserPreference user = userPreferences[userId];
            int[] userItems = user.ItemIds;

            int totalUsers = userPreferences.Count();
            int totalItemsOfPerson1 = userItems.Count();
            Dictionary<int, double> userSimilarities = new Dictionary<int, double>();

            for (int i = 0; i < totalUsers; i++)
            {
                if (i == userId)
                {
                    continue;
                }

                double som1 = 0;
                double som2 = 0;
                double som3 = 0;
                double total = 0.0;

                double averageRatingOfPerson1 = 0.0;
                double averageRatingOfPerson2 = 0.0;

                UserPreference current = userPreferences.ElementAt(i).Value;
                
                int[] indenticalItems = current.getIndenticalItems(userItems);

                averageRatingOfPerson1 = getAverageRating(current, indenticalItems);
                averageRatingOfPerson2 = getAverageRating(userPreferences[userId], indenticalItems);

                for (int k = 0; k < indenticalItems.Length; k++)
                {
                    int indexOfPerson1 = Array.FindIndex(userPreferences[userId].ItemIds, item => item == indenticalItems[k]);
                    double ratingOfPerson1 = userPreferences[userId].Preferences[indexOfPerson1];

                    int indexOfPerson2 = Array.FindIndex(current.ItemIds, item => item == indenticalItems[k]);
                    double ratingOfPerson2 = current.Preferences[indexOfPerson2];

                    som1 += (ratingOfPerson2 - averageRatingOfPerson1) * (ratingOfPerson1 - averageRatingOfPerson2);
                    som2 += Math.Pow(ratingOfPerson2 - averageRatingOfPerson1, 2);
                    som3 += Math.Pow(ratingOfPerson1 - averageRatingOfPerson2, 2);
                }

                total = som1 / (Math.Sqrt(som2) * Math.Sqrt(som3));
                userSimilarities.Add(userPreferences.ElementAt(i).Key, (Double.IsNaN(total) == true ? 0.0 : total));
            }

            return userSimilarities;
        }

        public Dictionary<int, double> calculateSimilarities(int userId, Dictionary<int, UserPreference> userPreferences)
        {
            UserPreference user = userPreferences[userId];
            int[] userItems = user.ItemIds;

            int totalUsers = userPreferences.Count();
            int totalItemsOfPerson1 = userItems.Count();
            Dictionary<int, double> userSimilarities = new Dictionary<int, double>();

            foreach (KeyValuePair<int, UserPreference> pair in userPreferences)
            {
                if (pair.Key == userId)
                {
                    continue;
                }

                double som1 = 0;
                double som2 = 0;
                double som3 = 0;
                double total = 0;

                double averageRatingOfPerson1 = 0.0;
                double averageRatingOfPerson2 = 0.0;

                UserPreference current  = userPreferences[pair.Key];
                //int[] indenticalItems = getIndenticalItems(userPreferences[i].ItemIds, userItems);
                int[] indenticalItems   = current.getIndenticalItems(userItems);

                if (indenticalItems.Length < 1)
                {
                    break;
                }

                averageRatingOfPerson1 = getAverageRating(current, indenticalItems);
                averageRatingOfPerson2 = getAverageRating(userPreferences[userId], indenticalItems);

                for (int k = 0; k < indenticalItems.Length; k++)
                {
                    int indexOfPerson1 = Array.FindIndex(userPreferences[userId].ItemIds, item => item == indenticalItems[k]);
                    double ratingOfPerson1 = userPreferences[userId].Preferences[indexOfPerson1];

                    int indexOfPerson2 = Array.FindIndex(current.ItemIds, item => item == indenticalItems[k]);
                    double ratingOfPerson2 = current.Preferences[indexOfPerson2];
                    
                    som1 += (ratingOfPerson2 - averageRatingOfPerson1) * (ratingOfPerson1 - averageRatingOfPerson2);
                    som2 += Math.Pow(ratingOfPerson2 - averageRatingOfPerson1, 2);
                    som3 += Math.Pow(ratingOfPerson1 - averageRatingOfPerson2, 2);
                }

                total = som1 / (Math.Sqrt(som2) * Math.Sqrt(som3));
                userSimilarities.Add(pair.Key, total);
            }

            return userSimilarities;
        }

        /*
         * Params:
         *          int[] itemsPerson1
         *          int[] itemsPerson2
         * 
         * Returns:
         *          int[] relatedItems
         * 
         * Vergelijkt 'itemsPerson1' met 'itemsPerson2' en zet de items die ze beide hebben in een nieuwe array.
         * 
         */
        private int[] getIndenticalItems(int[] itemsPerson1, int[] itemsPerson2)
        {
            int[] indenticalItems;
            List<int> items = new List<int>();

            for (int i = 0; i < itemsPerson1.Length; i++)
            {
                for (int j = 0; j < itemsPerson2.Length; j++)
                {
                    if (itemsPerson1[i] == itemsPerson2[j])
                    {
                        items.Add(itemsPerson1[i]);
                    }
                }
            }

            indenticalItems = items.ToArray();

            return indenticalItems;
        }

        /*
         * Params:
         *          UserPreference user
         *          int[] userItems
         * 
         * Returns:
         *          double averageRating
         * 
         * Loopt door alle items uit 'user' en vergelijkt die met 'userItems';
         * Als een item overeenkomt wordt de rating uit 'user' gehaalt;
         * Als de loop is voltooid wordt het gemiddelde berekent.
         * 
         */
        private double getAverageRating(UserPreference user, int[] userItems)
        {
            double totalItems = userItems.Length;
            double averageRating = 0.0;

            for (int i = 0; i < user.ItemIds.Length; i++)
            {
                for (int j = 0; j < userItems.Length; j++)
                {
                    if (user.ItemIds[i] == userItems[j])
                    {
                        averageRating += user.Preferences[i];
                    }
                }
            }

            averageRating = averageRating / totalItems;

            return averageRating;
        }

        /*
         * Params:
         *          Dictionary<int, double> userSimilarities
         *          Dictionary<int, UserPreference> userPreferences
         *          
         * Returns:
         *          Dictionary<int, double> rankings
         * 
         * Berekent de ranking door te loopen door de userSimularities en userPreferences. Beide indexen (userId) van de
         * dictionaries zijn hetzelfde.
         * 
         * De rankings worden in een nieuwe dictionary gezet waarbij de itemId als index wordt gebruikt.
         * 
         */
        public Dictionary<int, double> calculateRanking(Dictionary<int, double> userSimilarities, Dictionary<int, UserPreference> userPreferences, int userId)
        {
            UserPreference user1 = userPreferences[userId];

            // De som: similarity * rating van ieder item. Index = item.
            Dictionary<int, double> totalSimilarityRatings = new Dictionary<int, double>();

            // De som van alle similarities waardoor gedeeld moet worden. Index = item.
            Dictionary<int, double> totalSimilarity = new Dictionary<int, double>();

            // De ranking berekent voor ieder item. Index = item.
            Dictionary<int, double> rankings = new Dictionary<int, double>();

            foreach (KeyValuePair<int, double> pair in userSimilarities)
            {
                double similarity = userSimilarities[pair.Key];
                UserPreference user2 = userPreferences[pair.Key];

                if (user2 != null)
                {
                    IList<int> differentItems = getDifferentItems(user1, user2);

                    foreach (int item in differentItems)
                    {
                        // Zoek de index van de item die user1 niet heeft zodat we hiervan de rating in user2 met dezelfde index kunnen vinden.
                        int index = Array.IndexOf(user2.ItemIds, item);

                        if (index < 0)
                            continue;
                        
                        double rating = user2.Preferences[index];

                        if (totalSimilarityRatings.ContainsKey(item))
                        {
                            totalSimilarityRatings[item] += similarity * rating;
                            totalSimilarity[item] += similarity;
                        }
                        else
                        {
                            totalSimilarityRatings[item] = similarity * rating;
                            totalSimilarity[item] = similarity;
                        }
                    }
                }
            }

            // De ranking voor ieder item die user1 niet heeft berekenen.
            foreach (KeyValuePair<int, double> entry in totalSimilarityRatings)
            {
                rankings.Add(entry.Key, totalSimilarityRatings[entry.Key] / totalSimilarity[entry.Key]);
            }

            return rankings;
        }

        public Dictionary<int, double> calculateAllRanking(Dictionary<int, double> userSimilarities, Dictionary<int, UserPreference> userPreferences, int userId)
        {
            Dictionary<int, double> topSimilarities = (from pair in userSimilarities orderby pair.Value descending select pair).Take(10).ToDictionary(x => x.Key, x => x.Value);

            UserPreference user1 = userPreferences[userId];

            // De som: similarity * rating van ieder item. Index = item.
            Dictionary<int, double> totalSimilarityRatings = new Dictionary<int, double>();

            // De som van alle similarities waardoor gedeeld moet worden. Index = item.
            Dictionary<int, double> totalSimilarity = new Dictionary<int, double>();

            // De ranking berekent voor ieder item. Index = item.
            Dictionary<int, double> rankings = new Dictionary<int, double>();

            foreach (KeyValuePair<int, double> pair in topSimilarities)
            {
                double similarity = topSimilarities[pair.Key];
                UserPreference user2 = userPreferences[pair.Key];

                if (user2 != null)
                {
                    IList<int> differentItems = getDifferentItems(user1, user2);

                    foreach (int item in differentItems)
                    {
                        // Zoek de index van de item die user1 niet heeft zodat we hiervan de rating in user2 met dezelfde index kunnen vinden.
                        int index = Array.IndexOf(user2.ItemIds, item);

                        if (index < 0)
                            continue;

                        double rating = user2.Preferences[index];

                        if (totalSimilarityRatings.ContainsKey(item))
                        {
                            totalSimilarityRatings[item] += similarity * rating;
                            totalSimilarity[item] += similarity;
                        }
                        else
                        {
                            totalSimilarityRatings[item] = similarity * rating;
                            totalSimilarity[item] = similarity;
                        }
                    }
                }
            }

            // De ranking voor ieder item die user1 niet heeft berekenen.
            foreach (KeyValuePair<int, double> entry in totalSimilarityRatings)
            {
                rankings.Add(entry.Key, (Double.IsNaN(totalSimilarityRatings[entry.Key] / totalSimilarity[entry.Key]) == true ? 0.0 : totalSimilarityRatings[entry.Key] / totalSimilarity[entry.Key]));
            }

            return rankings;
        }

        /*
         * Params:
         *          UserPreference user1
         *          UserPreference user2
         *          
         * Returns:
         *          IList<int> items
         *          
         * Alle items van user1 en user2 worden met elkaar vergeleken. Wanneer user1 en user2 niet beide dezelfde item hebben wordt deze
         * aan een list toegevoegd.
         * 
         */
        public IList<int> getDifferentItems(UserPreference user1, UserPreference user2)
        {
            IList<int> items = new List<int>();

            int totalItemsUser1 = user1.ItemIds.Length;
            int totalItemsUser2 = user2.ItemIds.Length;

            if (totalItemsUser1 > totalItemsUser2)
            {
                for (int i = 0; i < totalItemsUser1; i++)
                {
                    if (!Array.Exists(user2.ItemIds, element => element == user1.ItemIds[i]))
                    {
                        items.Add(user1.ItemIds[i]);
                    }
                }
            }
            else
            {
                for (int i = 0; i < totalItemsUser2; i++)
                {
                    if (!Array.Exists(user1.ItemIds, element => element == user2.ItemIds[i]))
                    {
                        items.Add(user2.ItemIds[i]);
                    }
                }
            }

            return items;
        }       

        public Dictionary<int, UserPreference> CreateUsersFromFile()
        {
            Dictionary<int, UserPreference> userPreferences = new Dictionary<int, UserPreference>();

            try
            {
                string appdatafolder = Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, "App_Data");
                FileStream fStream = File.OpenRead( appdatafolder + "\\u.data");
                TextReader reader = new StreamReader(fStream);

                string line;

                while (!String.IsNullOrEmpty(line = reader.ReadLine()))
                {
                    if (line.Equals(""))
                        continue;

                    if (line.StartsWith("//"))
                        continue;

                    String[] s = line.Split('\t');

                    int u = Int32.Parse(s[0]);
                    int i = Int32.Parse(s[1]);
                    float r = float.Parse(s[2]);

                    UserPreference userPreference = null;

                    if (!userPreferences.TryGetValue(u, out userPreference))
                    {
                        userPreference = new UserPreference();
                        userPreference.UserId = u;
                        userPreferences.Add(u, userPreference);
                    }

                    userPreference.setItemIdRating(i, r);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }

            return userPreferences;
        }

        public Movie SearchMovie(int id)
        {
            Movie movie = null;

            try
            {
                string appdatafolder = Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, "App_Data");
                FileStream fStream = File.OpenRead(appdatafolder + "\\u.item");
                TextReader reader = new StreamReader(fStream);

                string line;

                while (!String.IsNullOrEmpty(line = reader.ReadLine()))
                {
                    if (line.Equals(""))
                        continue;

                    if (line.StartsWith("//"))
                        continue;

                    String[] s = line.Split('|');

                    int u = Int32.Parse(s[0]);
                    string i = s[1];
                    string r = s[2];

                    if (u == id)
                    {
                        movie = new Movie();
                        movie.Id = u;
                        movie.Title = i;
                        movie.Date = r;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }

            return movie;
        }
    }
}